#ifndef CONNECTIONS_H
#define CONNECTIONS_H

#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <string>


// TODO: close socket if false
template <class socket_t>
class linux_connection_server
{
public:
    static const unsigned int MAX_QUEUE_LEN = 10;

private:
    struct sockaddr_in  __server_addr;
    u_int16_t           __port;
    socket_t            __listening_socket;

public:
    explicit linux_connection_server(const u_int16_t port)
        : __port(port),
          __listening_socket(0)
    {}

    inline bool init()
    {
        // create tcp/ip socket
        if ((__listening_socket = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
        {
            return false;
        }

        // filling socket address
        __server_addr.sin_family = AF_INET;
        __server_addr.sin_port = htons(__port);
        __server_addr.sin_addr.s_addr = INADDR_ANY;

        // binding socket with this address
        if (::bind(__listening_socket, (struct sockaddr*)&__server_addr, sizeof(__server_addr)) == -1)
        {
            return false;
        }

        // server ready for connection
        // set maximum queue length
        if (::listen(__listening_socket, MAX_QUEUE_LEN) == -1)
        {
            return false;
        }

        return true;
    }

    inline bool get_connection(socket_t &sock)
    {
        struct sockaddr_in  client_addr;
        unsigned int len = sizeof(client_addr);
        if ((sock = ::accept(__listening_socket, (struct sockaddr*)&client_addr, &len)) == -1)
        {
            return false;
        }

        return true;
    }

};

template <class socket_t>
class linux_connection_client
{
private:
    struct sockaddr_in  __server_addr;
    u_int16_t           __port;
    std::string         __ip;
    socket_t            __sock;

public:
    linux_connection_client(const u_int16_t port, const std::string &ip_addr)
        : __port(port),
          __ip(ip_addr)
    {}

    inline bool init()
    {
        // create socket
        if ((__sock = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
        {
            return false;
        }

        // filling socket address
        __server_addr.sin_family = AF_INET;
        __server_addr.sin_port = htons(__port);

        if (inet_pton(AF_INET, __ip.c_str(), &__server_addr.sin_addr) < 0)
        {
            return false;
        }

        return true;
    }

    inline bool connect(socket_t &sock)
    {
        // connect to server
        if (::connect(__sock, (struct sockaddr*)&__server_addr, sizeof(__server_addr)) == -1)
        {
            return false;
        }

        sock = __sock;

        return true;
    }
};




template <template <class> class connection_t, class socket_t>
class connection_sever
{
private:
    connection_t <socket_t> __connection;

public:
    connection_sever(const u_int16_t port)
        : __connection(port)
    {
    }

    inline bool init()
    {
        return __connection.init();
    }

    inline bool get_connection(socket_t &sock)
    {
        return __connection.get_connection(sock);
    }

};


template <template <class> class connection_t, class socket_t>
class connection_client
{
private:
    connection_t <socket_t> __connection;

public:
    connection_client(const u_int16_t port, const std::string &ip_addr)
        : __connection(port, ip_addr)
    {
    }

    inline bool init()
    {
        return __connection.init();
    }

    inline bool connect(socket_t &sock)
    {
        return __connection.connect(sock);
    }
};


#endif // CONNECTIONS_H
